// REVISED

package com.gorkemozbek.rubic.util.struct

sealed abstract class Structure {

  import Structure.Impl._

  override def toString: String = this match {
    case UNDEFINED => "undefined"
    case BOOLEAN(x) => x.toString
    case NUMERIC(x) => if (x.toInt.toDouble == x) x.toInt.toString else x.toString
    case TEXTUAL(x) => x
    case SET(x) => x mkString("(", ", ", ")")
    case LIST(x) => x mkString("{", ", ", "}")
    case TABLE(x) => x map { e => e._1.toString + " => " + e._2.toString} mkString("[", ", ", "]")
  }

}

object Structure {

  import scala.collection.immutable.{List, Set}

  type FeatureEntry = (Feature, Structure)

  type FeatureList = List[FeatureEntry]

  type StructureSet = Set[Structure]

  type StructureList = List[Structure]

  sealed abstract class UndefinedStruct extends Structure

  sealed abstract class BooleanStruct extends Structure

  sealed abstract class NumericStruct extends Structure

  sealed abstract class TextualStruct extends Structure

  sealed abstract class SetStruct extends Structure

  sealed abstract class ListStruct extends Structure

  sealed abstract class TableStruct extends Structure

  private object Impl {

    case object UNDEFINED extends UndefinedStruct

    case class BOOLEAN(x: Boolean) extends BooleanStruct

    case class NUMERIC(x: Double) extends NumericStruct

    case class TEXTUAL(x: String) extends TextualStruct

    case class SET(x: StructureSet) extends SetStruct

    case class LIST(x: StructureList) extends ListStruct

    case class TABLE(x: FeatureList) extends TableStruct

  }

  import Impl._

  object Conversions {

    implicit def undefinedConv(x: Unit): UndefinedStruct = undefined

    implicit def booleanConv(x: Boolean): BooleanStruct = boolean(x)

    implicit def numericConv(x: Byte): NumericStruct = numeric(x)

    implicit def numericConv(x: Short): NumericStruct = numeric(x)

    implicit def numericConv(x: Int): NumericStruct = numeric(x)

    implicit def numericConv(x: Long): NumericStruct = numeric(x)

    implicit def numericConv(x: Float): NumericStruct = numeric(x)

    implicit def numericConv(x: Double): NumericStruct = numeric(x)

    implicit def textualConv(x: String): TextualStruct = textual(x)

    implicit def textualConv(x: Char): TextualStruct = textual(x)

  }

  implicit object FeatureOrdering extends Ordering[Feature] {

    def compare(f1: Feature, f2: Feature): Int = f1.name compareTo f2.name

  }
  
  def undefined: UndefinedStruct = UNDEFINED

  def boolean(x: Boolean): BooleanStruct = BOOLEAN(x)

  def numeric(x: Byte): NumericStruct = NUMERIC(x.toDouble)

  def numeric(x: Short): NumericStruct = NUMERIC(x.toDouble)

  def numeric(x: Int): NumericStruct = NUMERIC(x.toDouble)

  def numeric(x: Long): NumericStruct = NUMERIC(x.toDouble)

  def numeric(x: Float): NumericStruct = NUMERIC(x.toDouble)

  def numeric(x: Double): NumericStruct = NUMERIC(x.toDouble)

  def textual(x: Char): TextualStruct = TEXTUAL(x.toString)
  
  def textual(x: String): TextualStruct = TEXTUAL(x)

  def set(x: Structure*): SetStruct = SET(Set(x: _*))

  def list(x: Structure*): ListStruct = LIST(List(x: _*))

  def table(x: FeatureEntry*): TableStruct = TABLE(x sortBy { _._1 } toList)


  def isUndefined(s: Structure): Boolean = s match {
    case UNDEFINED => true
    case _ => false
  }

  def isBoolean(s: Structure): Boolean = s match {
    case BOOLEAN(_) => true
    case _ => false
  }

  def isNumeric(s: Structure): Boolean = s match {
    case NUMERIC(_) => true
    case _ => false
  }

  def isTextual(s: Structure): Boolean = s match {
    case TEXTUAL(_) => true
    case _ => false
  }

  def isSet(s: Structure): Boolean = s match {
    case SET(_) => true
    case _ => false
  }

  def isList(s: Structure): Boolean = s match {
    case LIST(_) => true
    case _ => false
  }

  def isTable(s: Structure): Boolean = s match {
    case TABLE(_) => true
    case _ => false
  }

  def isCollection(s: Structure): Boolean = isSet(s) || isList(s) || isTable(s)

  def booleanValue(s: BooleanStruct): Boolean = s match {
    case BOOLEAN(x) => x
  }

  def byteValue(s: NumericStruct): Byte = s match {
    case NUMERIC(x) => x.toByte
  }

  def shortValue(s: NumericStruct): Short = s match {
    case NUMERIC(x) => x.toShort
  }

  def intValue(s: NumericStruct): Int = s match {
    case NUMERIC(x) => x.toInt
  }

  def longValue(s: NumericStruct): Long = s match {
    case NUMERIC(x) => x.toLong
  }

  def floatValue(s: NumericStruct): Float = s match {
    case NUMERIC(x) => x.toFloat
  }

  def doubleValue(s: NumericStruct): Double = s match {
    case NUMERIC(x) => x
  }

  def stringValue(s: TextualStruct): String = s match {
    case TEXTUAL(x) => x
  }

  def setValue(s: SetStruct): StructureSet = s match {
    case SET(x) => x
  }

  def listValue(s: ListStruct): StructureList = s match {
    case LIST(x) => x
  }

  def listValue(s: TableStruct): FeatureList = s match {
    case TABLE(x) => x
  }

  def iterate(s: Structure): Iterator[Structure] = s match {
    case UNDEFINED => None.iterator
    case x @ BOOLEAN(_) => Some(x).iterator
    case x @ NUMERIC(_) => Some(x).iterator
    case x @ TEXTUAL(_) => Some(x).iterator
    case SET(x) => x.iterator
    case LIST(x) => x.iterator
    case TABLE(x) => x map { _._2 } iterator
  }

  def iterateEntries(s: TableStruct): Iterator[FeatureEntry] = s match {
    case TABLE(x) => x.iterator
  }

  def lookup(s: Structure, f: Feature): Option[Structure] = s match {
    case TABLE(x) => x find { _._1 eq f } map { _._2 }
    case _ => None
  }

  def lookupAll(s: Structure, f: Feature): StructureList = s match {
    case TABLE(x) => x dropWhile { _._1 ne f } takeWhile { _._1 eq f } map { _._2 }
    case _ => Nil
  }

  def find(s: Structure, fs: Feature*): Option[Structure] = {
    if (fs.isEmpty) Some(s)
    else lookup(s, fs.head) match {
      case Some(x) => find(x, fs.tail: _*)
      case None => None
    }
  }

  def findAll(s: Structure, fs: Feature*): StructureList = {
    if (fs.isEmpty) Nil
    else lookupAll(s, fs.head) flatMap { struct => findAll(struct, fs.tail: _*) }
  }

}
